package service

import (
	"context"
	"encoding/json"
	"github.com/dubbogo/grpc-go/status"
	"github.com/form3tech-oss/jwt-go"
	"strconv"
	"time"
	"user/api"

	"dubbo.apache.org/dubbo-go/v3/config"
	_ "dubbo.apache.org/dubbo-go/v3/imports"
	"golang.org/x/crypto/bcrypt"
)

var commonProvider *api.CommonClientImpl

func OK(value *api.User, token string) (*api.Token, error) {
	return &api.Token{
		Id:    value.Id,
		Token: token,
	}, nil
}

func FAIL(err error) (*api.Token, error) {
	return nil, err
}

type UserProvider struct {
	api.UnimplementedUserProviderServer
}

func (*UserProvider) Login(ctx context.Context, user *api.User) (*api.Token, error) {
	bytes, err := commonProvider.Get(ctx, &api.Query{
		Database: "douyin",
		Table:    "user",
		Key:      []string{"id", "username", "password"},
		Where: []*api.Query_Condition{
			{
				Key:   []string{"username"},
				Value: []string{user.Username},
				Type:  []api.Query_Condition_ConditionType{api.Query_Condition_NE},
			},
		},
		Result: api.Query_USER,
	})
	if err != nil {
		return FAIL(err)
	}

	var list []*api.User
	err = json.Unmarshal(bytes.Value, &list)
	if err != nil {
		return FAIL(err)
	}

	//无该用户  报错
	if list == nil || len(list) < 1 {
		return FAIL(status.Error(1, "User Not Found"))
	}

	//验证密码
	result := list[0]

	if !ComparePasswords(result.Password, user.Password) {
		return FAIL(status.Error(1, "Username or Password Error"))
	}

	// 生成新token
	token, err := GenerateToken(result)
	if err != nil {
		return FAIL(err)
	}

	//存redis
	_, err = commonProvider.SetRedis(ctx, &api.Value{
		Database: "douyin",
		Table:    "user",
		Key:      []string{"token", strconv.FormatInt(user.Id, 10)},
		Value:    []string{token},
	})
	if err != nil {
		return FAIL(err)
	}

	return OK(result, token)
}

func (*UserProvider) Register(ctx context.Context, user *api.User) (*api.Token, error) {
	//数据库查名字
	count, err := commonProvider.Count(ctx, &api.Query{
		Database: "douyin",
		Table:    "user",
		Where: []*api.Query_Condition{
			{
				Key:   []string{"username"},
				Value: []string{user.Username},
				Type:  []api.Query_Condition_ConditionType{api.Query_Condition_NE},
			},
		},
		Result: api.Query_USER,
	})
	if err != nil {
		return FAIL(err)
	}
	//已有同名用户
	if count.Size != 0 {
		return FAIL(status.Error(0, "User Already Exists"))
	}

	// 无同名用户，则创建新用户并保存到数据库中
	user.Id = time.Now().Unix()
	user.Password, err = HashAndSalt(user.Password)
	_, err = commonProvider.Update(ctx, &api.Value{
		Database: "douyin",
		Table:    "user",
		Key:      []string{"id", "username", "password"},
		Value:    []string{strconv.FormatInt(user.Id, 10), user.Username, user.Password},
	})
	if err != nil {
		return FAIL(err)
	}

	// 生成token
	token, err := GenerateToken(user)
	if err != nil {
		return FAIL(err)
	}

	//存redis
	_, err = commonProvider.SetRedis(ctx, &api.Value{
		Database: "douyin",
		Table:    "user",
		Key:      []string{"token", strconv.FormatInt(user.Id, 10)},
		Value:    []string{token},
	})
	if err != nil {
		return FAIL(err)
	}

	return OK(user, token)
}

func (*UserProvider) Find(ctx context.Context, user *api.User) (*api.User, error) {
	//根据id查数据库
	bytes, err := commonProvider.Get(ctx, &api.Query{
		Database: "douyin",
		Table:    "user",
		Key:      []string{"id", "username"},
		Where: []*api.Query_Condition{
			{
				Key:   []string{"id"},
				Value: []string{strconv.FormatInt(user.Id, 10)},
				Type:  []api.Query_Condition_ConditionType{api.Query_Condition_NE},
			},
		},
		Result: api.Query_USER,
	})
	if err != nil {
		return nil, err
	}
	var list []*api.User
	err = json.Unmarshal(bytes.Value, &list)
	if err != nil {
		return nil, err
	}
	return list[0], nil
}

func (*UserProvider) Authenticate(ctx context.Context, token *api.Token) (*api.User, error) {
	if token.Token == "" {
		return &api.User{Id: 1}, nil
	}

	// 1.token中解析出id
	claims := &jwt.StandardClaims{}
	//解码token字符串到claims中
	_, err := jwt.ParseWithClaims(token.Token, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte("secret"), nil
	})
	if err != nil {
		return nil, err
	}
	//转为int64
	id, err := strconv.ParseInt(claims.Id, 10, 64)
	if err != nil {
		return nil, err
	}
	//// 2. 根据用户ID查询存redis中的token
	//bytes, err := commonProvider.GetRedis(ctx, &api.Query{
	//	Database: "douyin",
	//	Table:    "user",
	//	Key:      []string{"token", strconv.FormatInt(id, 10)},
	//})
	//if err != nil {
	//	return nil, err
	//}
	//var list string
	//err = json.Unmarshal(bytes.Value, &list)
	//if err != nil {
	//	return nil, err
	//}
	//// 3. 验证相关信息
	////无该用户
	//if list == "" {
	//	return nil, status.Error(0, "User Not Login")
	//}

	// 4. 返回用户Id
	return &api.User{
		Id: id,
	}, nil
}

// GenerateToken NewToken 根据User信息创建token
func GenerateToken(u *api.User) (string, error) {
	expiresTime := time.Now().Add(time.Hour * 24 * 7).Unix()
	id64 := u.Id
	claims := &jwt.StandardClaims{
		Audience:  []string{u.Username},
		ExpiresAt: expiresTime,
		Id:        strconv.FormatInt(id64, 10),
		IssuedAt:  time.Now().Unix(),
		Issuer:    "tiktok",
		NotBefore: time.Now().Unix(),
		Subject:   "token",
	}
	// 2. 创建一个token
	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 3. 对token进行签名
	if token, err := tokenClaims.SignedString([]byte("secret")); err == nil {
		return token, nil
	} else {
		return "", status.Error(1, "token err")
	}
}

// 加密密码
func HashAndSalt(pwdStr string) (pwdHash string, err error) {
	pwd := []byte(pwdStr)
	hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	pwdHash = string(hash)
	return pwdHash, nil
}

// 验证密码
func ComparePasswords(hashedPwd string, plainPwd string) bool {
	byteHash := []byte(hashedPwd)
	bytePwd := []byte(plainPwd)
	err := bcrypt.CompareHashAndPassword(byteHash, bytePwd)
	if err != nil {
		return false
	}
	return true
}

func init() {
	// 公共服务
	commonProvider = new(api.CommonClientImpl)

	// 注册服务
	config.SetProviderService(&UserProvider{})
	config.SetConsumerService(commonProvider)
}
